-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require "mc.class"
local abstract_cfg = require "pojo.boot.abstract_boot_option"
local boot_def = require "macros.boot_def"
local mode_hander = require "handler.mode_handler"
local effective_hander = require "handler.effective_time_handler"
local start_option_handler = require "handler.start_option_handler"
local bs_util = require 'util.base_util'
local log = require 'mc.logging'
local bios_factory = require 'factory.bios_factory'
local obj_def = require 'macros.object_def'
local ipmi = require 'ipmi'
local boot_config_activate_service = require 'service.boot_config_activate_service'
local pro_global = require 'macros.property_global'
local context = require 'mc.context'

local boot_flag = class(abstract_cfg)

local effective_times_handler_tbl = {}
local boot_mode_handler_tbl = {}
local start_option_handler_tbl = {}

function boot_flag:ctor(option_db_obj, system_id)
    boot_flag.super.init_db_info(self, option_db_obj, boot_def.BOOT_FLAGS, boot_def.BOOT_FLAGS_LOG, system_id)

    effective_times_handler_tbl[boot_def.EFFECTIVE_NONE] = effective_hander.effective_none_handler
    effective_times_handler_tbl[boot_def.EFFECTIVE_ONCE] = effective_hander.effective_once_handler
    effective_times_handler_tbl[boot_def.EFFECTIVE_FOREVER] = effective_hander.effective_forever_handler
    boot_mode_handler_tbl[boot_def.BIOS_BOOT_LEGACY] = mode_hander.legacy_handler
    boot_mode_handler_tbl[boot_def.BIOS_BOOT_UEFI] = mode_hander.uefi_handler
    start_option_handler_tbl[0] = start_option_handler.option_handler
end

local function valid_config(data)
    data[0] = data[0] & 0xff
    data[0] = data[0] | boot_def.PBIT7
end

function boot_flag:get_bmc_enable()
    local data = self.data
    if not data or not data[1] then
        log:error("get_bmc_enable: data invalid")
        return boot_def.BMC_ENABLE
    end

    data[1] = data[1] & 0xff
    local enable_flag = data[1] & boot_def.PBIT7
    -- 非cmos
    if enable_flag == 0 then
        return boot_def.BMC_ENABLE
    end

    return boot_def.BMC_DISABLE
end

-- 设置启动模式
function boot_flag:set_boot_mode(boot_mode)
    local data = self.data
    if not boot_mode or not data or not data[0] or
        not boot_mode_handler_tbl then
        log:error("set_boot_mode: param invalid.")
        return false
    end

    local mode_handler = boot_mode_handler_tbl[boot_mode]
    if not mode_handler then
        log:error("set_boot_mode: mode_handler is null.")
        return false
    end

    -- 设置启动模式,至少让其生效
    valid_config(data)
    -- 设置启动模式
    local res = mode_handler.handle(data)
    if not res then
        log:error("set_boot_mode: handler fail.")
        return res
    end

    boot_config_activate_service.get_instance():update_config_boot_mode(boot_mode)

    return self:update_db_info()
end

-- 设置启动项生效次数
function boot_flag:set_effective_time(times)
    local data = self.data
    if not times or not data or not effective_times_handler_tbl then
        log:error("set_effective_time: param invalid.")
        return false
    end

    local times_handler = effective_times_handler_tbl[times]
    if not times_handler then
        log:error("set_effective_time: times_handler is null.")
        return false
    end

    local res = times_handler.handle(data)
    if not res then
        log:error("set_effective_time: handler fail.")
        return res
    end

    return self:update_db_info()
end

-- 设置启动项，并确保至少生效一次，如果之前是永久生效的话，则永久生效
function boot_flag:set_start_option(start_option, current_effective_times)
    local data = self.data
    if not start_option or not data or not start_option_handler_tbl or
        not current_effective_times then
        log:error("boot_flag: set_start_option param invalid.")
        return false
    end

    local opt_handler = start_option_handler_tbl[0]
    if not opt_handler then
        log:error("boot_flag: set_start_option opt_handler is null.")
        return false
    end

    local res = opt_handler.handle(data, start_option)
    if not res then
        log:error("boot_flag: set_start_option handler fail.")
        return res
    end

    res = self:set_effective_time(boot_def.EFFECTIVE_ONCE)
    if not res then
        return res
    end

    if current_effective_times == boot_def.EFFECTIVE_FOREVER then
        res = self:set_effective_time(boot_def.EFFECTIVE_FOREVER)
        if not res then
            return res
        end
    end

    return self:update_db_info()
end

local format_tbl = {
    [0] = '<<reserved:5, boot_type:1, effect_opt:1, flags_valid:1>>',
    [1] = '<<lockout_rst_btn:1, screen_blank:1, boot_dev:4, lock_kbd:1, clear_cmos:1>>',
    [2] = '<<cons_redirection_ctrl:2, lockout_sleep_btn:1, upw_bypass:1, ' ..
        'force_pet:1, bios_fw_verbose:2, lockout_via_power_btn:1>>',
    [3] = '<<mux_ctrl_override:3, shared_override:1, reserved:4>>',
    [4] = '<<dev_selector:5, reserved:3>>'
}

function boot_flag:fetch_boot_flag(config_data)
    local params_info = {}
    for i = 1, boot_def.BIOS_BOOT_FLAGS_CMD do
        local bin = string.sub(config_data, i, i)
        local param_info = bs_util.binary_to_struct(bin, format_tbl[i - 1])
        params_info[i - 1] = param_info
    end
    local boot_ser = bios_factory.get_service('boot_service')
    self.origin_boot_dev_name = ""
    if boot_ser then
        self.origin_boot_dev_name =
            boot_ser:get_boot_prop(obj_def.BOOT_SOURCE_OVERRIDE_TARGET, self.system_id)
    end
    return params_info
end

function boot_flag:validate_boot_dev(params_info)
    local boot_dev = boot_def.NO_OVERRIDE
    self.ipmi_bmc_enable = boot_def.BMC_DISABLE
    -- 非清除配置、并且标记有效的情况下
    if params_info[1].clear_cmos == 0 and params_info[0].flags_valid ~= 0 then
        boot_dev = params_info[1].boot_dev
        if boot_dev == boot_def.FORCE_SAFE_MODE or
            boot_dev == boot_def.FORCE_DIAGNOSTIC_PARTITION then
            log:error("validate_boot_dev: boot_dev(%d) invalid.", boot_dev)
            return false
        end
        self.ipmi_bmc_enable = boot_def.BMC_ENABLE
    end
    self.ipmi_boot_dev = boot_dev
    params_info[1].boot_dev = boot_dev
    return true
end

function boot_flag:validate_boot_mode(params_info)
    -- 目前只支持UEFI模式
    -- 如果设置为Leacy则忽略，替换成BMC上次保存的启动类型即Uefi
    local mode = params_info[0].boot_type
    if mode == boot_def.BIOS_BOOT_LEGACY then
        params_info[0].boot_type = boot_def.BIOS_BOOT_UEFI
        return true
    end
    return true
end

function boot_flag:is_option_vaild(start_option)
    if not boot_def.start_option_to_order[start_option] then
        return false
    else
        return true
    end
end

function boot_flag:update_boot_info(params_info, ctx)
    local boot_ser = bios_factory.get_service('boot_service')
    local boot_options_ser = bios_factory.get_service('boot_options_service')
    if not boot_ser or not boot_options_ser then
        log:error("update_boot_info: boot_ser is null.")
        return false
    end

    -- 设置启动项生效次数
    local boot_eft = params_info[0].effect_opt
    local effective_tims = boot_def.EFFECTIVE_FOREVER
    if self.ipmi_bmc_enable == boot_def.BMC_DISABLE then
        effective_tims = boot_def.EFFECTIVE_NONE
    elseif boot_eft == 0 then
        effective_tims = boot_def.EFFECTIVE_ONCE
    end
    local pre_forever_option = boot_ser:get_prop("PreviousBootSourceOverrideTarget", self.system_id)
    if boot_options_ser:is_boot_option_policy(self.system_id) and
        boot_options_ser:receive_from_bios(ctx, self.system_id) and self:is_option_vaild(pre_forever_option) then
        effective_tims = boot_def.EFFECTIVE_FOREVER
        params_info[1].boot_dev = boot_def.start_option_tbl[pre_forever_option]
    end
    boot_ser:set_start_option_flag(effective_tims, self.system_id)
    local boot_dev = params_info[1].boot_dev
    local start_option_string = boot_def.start_option_string_tbl[boot_dev]
    if not start_option_string then
        start_option_string = ""
    end
    -- 设置启动项
    boot_ser:set_boot_prop(obj_def.BOOT_SOURCE_OVERRIDE_TARGET, start_option_string, self.system_id)

    if boot_options_ser:is_boot_option_policy(self.system_id) and effective_tims == boot_def.EFFECTIVE_FOREVER and 
        not boot_options_ser:receive_from_bios(ctx, self.system_id) then
        log:notice('[bios] start option policy, update_boot_option %s EFFECTIVE_FOREVER', start_option_string)
        boot_options_ser:set_boot_type_order_1st(context.new(), start_option_string, self.system_id)
    end

    local ok, err = pcall(boot_ser.clear_forever_option, boot_ser, context.new(), self.system_id)
    if not ok then
        log:error('[bios] clear forever option failed, err : %s', err)
    end

    log:notice('[bios]update_boot_info, system(%s) set start option to %s', self.system_id, start_option_string)
    return true
end

function boot_flag:save_ipmi_data(params_info)
    local data = self.data
    if not data then
        return false
    end

    for i = 1, boot_def.BIOS_BOOT_FLAGS_CMD do
        local index = i - 1
        local bin_byte = bs_util.struct_to_binary(params_info[index], format_tbl[index]):byte()
        self.data[index] = bin_byte
        log:info("option[%d]: %d", index, bin_byte)
    end

    return true
end

function boot_flag:save_operator_log(ctx)
    local boot_dev_name = boot_def.start_option_string_tbl[self.ipmi_boot_dev]
    local origin_boot_dev_name = self.origin_boot_dev_name
    if not boot_dev_name then
        boot_dev_name = ""
    end
    if not origin_boot_dev_name then
        origin_boot_dev_name = ""
    end
    local option_bin = self:get_data_string()
    if boot_dev_name == origin_boot_dev_name then
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set boot option %s to (RAW:%s)," ..
            "and set boot device to (%s) successfully", self.log_prop, option_bin, boot_dev_name)
    else
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set boot option %s to (RAW:%s)," ..
            "and set boot device from (%s) to (%s) successfully", self.log_prop,
            option_bin, origin_boot_dev_name, boot_dev_name)
    end
end

function boot_flag:set_boot_info(config_data, ctx)
    -- 解析ipmi数据
    local params_info = self:fetch_boot_flag(config_data)

    -- 校验ipmi数据的启动项
    local res = self:validate_boot_dev(params_info)
    if not res then
        return boot_def.COMP_CODE_PARAM_NOT_SUPPORTED
    end

    -- 校验ipmi数据的启动模式
    res = self:validate_boot_mode(params_info)
    if not res then
        return boot_def.COMP_CODE_PARAM_NOT_SUPPORTED
    end

    return boot_def.COMP_CODE_SUCCESS, params_info
end

-- IPMI设置启动参数
function boot_flag:operate_info(config_data, ctx)
    if #config_data > boot_def.BIOS_BOOT_VALID_NUM then
        return boot_def.COMP_CODE_LEN_INVALID
    end
    local result, params_info = self:set_boot_info(config_data, ctx)
    if result ~= boot_def.COMP_CODE_SUCCESS then
        log:error("boot_flag: set_boot_dev fail.")
        return result
    end
    -- 是否应该应该启动自动清除功能
    local boot_options_ser = bios_factory.get_service('boot_options_service')
    if boot_options_ser:auto_clear_valid(ctx) then
        log:notice('auto clear valid is true, start to create countdown task')
        -- 如果需要自动清除新起倒计时任务
        boot_config_activate_service.get_instance():create_countdown_task(params_info, ctx)
        pro_global.G_SET_BIOS_OPTION = boot_def.TaskFlag.LastIpmi
        pro_global.G_SET_BIOS_OPTION_FLAG = boot_def.TaskFlag.LastIpmi
    else
        local ok, res = pcall(function ()
            return boot_config_activate_service.get_instance():deal_cmos(params_info, ctx, self.system_id)
        end)
        if not ok or not res then
            log:error('deal_cmos fail.')
        end
        log:notice('auto clear valid is false, save config')
        -- 根据ipmi的数据更新启动项、启动生效次数
        ok = self:update_boot_info(params_info, ctx)
        if not ok then
            log:error("set_boot_info: update_boot_info fail.")
            return boot_def.COMP_CODE_UNKNOWN
        end
        -- 保存数据
        ok = self:save_ipmi_data(params_info)
        if not ok then
            log:error("operate_info: save_ipmi_data fail.")
            return boot_def.COMP_CODE_UNKNOWN
        end
    end

    return boot_def.COMP_CODE_SUCCESS
end

function boot_flag:get_boot_message()
    local config_data_pos_1 = self.config_data_pos_1
    if not config_data_pos_1 then
        return nil
    end

    return config_data_pos_1.boot_dev
end

return boot_flag